package com.db4o.odbgen.plugins.java.enhancements;

import java.util.ArrayList;
import java.util.List;

import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;

import com.db4o.internal.odbgen.TemplatesUtils;
import com.db4o.internal.odbgen.plugins.TemplateList;
import com.db4o.internal.odbgen.plugins.java.enhancements.ConstructorXmlSchemaElement;
import com.db4o.odbgen.OdbgenException;
import com.db4o.odbgen.TargetLanguage;
import com.db4o.odbgen.plugins.MainGeneratorPlugin;
import com.db4o.odbgen.plugins.Plugin;
import com.db4o.odbgen.plugins.java.maingenerator.MainGenerator;
import com.db4o.odbgen.plugins.java.maingenerator.typedxmlschema.TSClass;
import com.db4o.odbgen.plugins.java.maingenerator.typedxmlschema.TSField;
import com.db4o.odbgen.plugins.java.maingenerator.typedxmlschema.TSMethod;
import com.db4o.odbgen.plugins.java.maingenerator.typedxmlschema.TSMethodParameter;
import com.db4o.odbgen.plugins.java.maingenerator.typedxmlschema.TSModifier;

public class Constructor implements Plugin{

	private MainGenerator _mainGenerator;

	/**
	 * Contains the list of standard templates used by {@link Constructor}.
	 * @author liviug
	 */
	private enum Templates implements TemplateList{
		CONSTRUCTOR(Templates.path, "constructor.vm");
		private static final String path = "standardPlugins/java/enhancements";

		
		private String _path;
		private String _name;

		Templates(String path, String name){
			this._path = path;
			this._name = name;
		}
		
		/**
		 * Returns the path and the name of the template.
		 */
		public String getQualifiedName() {
			return this._path+"/"+this._name;
		}
	}
	
	/**
	 * See {@link Plugin} for details.
	 */
	@Override
	public List<Class<? extends Plugin>> getPluginDependencies() {
		ArrayList<Class<? extends Plugin>> res = new ArrayList<Class<? extends Plugin>>();
		res.add(MainGenerator.class);
		return res;
	}

	/**
	 * See {@link Plugin} for details.
	 */
	@Override
	public void initialize(MainGeneratorPlugin mainGenerator) {
		this._mainGenerator = (MainGenerator)mainGenerator;
		
	}

	/**
	 * See {@link Plugin} for details.
	 */
	@Override
	public void start() throws OdbgenException {
		for(TSClass tsClass : this._mainGenerator.getTypedXmlSchema().getClasses()){
			//Only classes included inside XmlSchema are considered.
			if(tsClass.getElement()==null) continue;
			if(tsClass.getElement().getBooleanChildValue(ConstructorXmlSchemaElement.CREATECONSTRUCTOR.getName())){
				this.createConstructor(tsClass);
			}
		}
	}

	/**
	 * Creates a constructor for the received class.
	 * @throws OdbgenException 
	 */
	private void createConstructor(TSClass tsClass) throws OdbgenException {
		//Get the fields that should be initialized.
		List<TSField> parameterFields = new ArrayList<TSField>();
		for(TSField field : tsClass.getFields()){
			if(field.getElement()==null) continue;
			
			if(field.getElement().getBooleanChildValue(ConstructorXmlSchemaElement.CONSTRUCTOREXCLUDEFIELD.getName())){
				//A field that should be excluded from the parameter list.
				//Do nothing here.
			}
			else{
				//A field that should be included in the list of parameters.
				parameterFields.add(field);
			}
		}

		//Build constructor parameters.
		List<TSMethodParameter> methodParameters = new ArrayList<TSMethodParameter>();
		for(TSField field : parameterFields){
			TSMethodParameter mparam = new TSMethodParameter(field.getName(), field.getType());
			methodParameters.add(mparam);
		}

		//Build the content
		Template template = TemplatesUtils.getTemplateJar(Constructor.Templates.CONSTRUCTOR.getQualifiedName(), this._mainGenerator.getStandardTemplatesJarName());
		VelocityContext context = new VelocityContext();
		context.put("parameterFields", parameterFields);
		context.put("className", tsClass.getSimpleName());
		String content = TemplatesUtils.merge(template, context);
		
		//Build the constructor.
		TSMethod constructor = new TSMethod(
				true,
				TSModifier.getPublicModifier(),
				null,
				tsClass.getSimpleName(),
				methodParameters,
				content
				);
		
		//Add the constructor as the first method.
		tsClass.getMethods().add(0, constructor);

		
		//Determine whether we create a non argument constructor.
		boolean createNonArgumentConstructor;
		if(tsClass.getElement().hasSingleChild(ConstructorXmlSchemaElement.CREATENOARGUMENTCONSTRUCTOR.getName())){
			if(tsClass.getElement().getBooleanChildValue(ConstructorXmlSchemaElement.CREATENOARGUMENTCONSTRUCTOR.getName())){
				createNonArgumentConstructor = true;
			}
			else{
				createNonArgumentConstructor = false;
			}
		}
		else{
			//This is the default value
			createNonArgumentConstructor = true;
		}
		
		if(createNonArgumentConstructor){
			//Build the non argument constructor.
			constructor = new TSMethod(
					true,
					TSModifier.getPublicModifier(),
					null,
					tsClass.getSimpleName(),
					new ArrayList<TSMethodParameter>(),
					"" //No content
					);
			
			//Add the non arg constructor as the first method.
			tsClass.getMethods().add(0, constructor);
		}
		
	}

	/**
	 * See {@link Plugin} for details.
	 */
	@Override
	public boolean supportsTargetLanguage(TargetLanguage targetLanguage) {
		if(targetLanguage == TargetLanguage.JAVA){
			return true;
		}
		return false;
	}

}
